( function () {
/*
 *	 PVR v2 (legacy) parser
 *   TODO : Add Support for PVR v3 format
 *   TODO : implement loadMipmaps option
 */

class PVRLoader extends THREE.CompressedTextureLoader {
  constructor(manager) {
    super(manager);
  }

  parse(buffer, loadMipmaps) {
    const headerLengthInt = 13;
    const header = new Uint32Array(buffer, 0, headerLengthInt);
    const pvrDatas = {
      buffer: buffer,
      header: header,
      loadMipmaps: loadMipmaps
    };

    if (header[0] === 0x03525650) {
      // PVR v3
      return _parseV3(pvrDatas);
    } else if (header[11] === 0x21525650) {
      // PVR v2
      return _parseV2(pvrDatas);
    } else {
      console.error('THREE.PVRLoader: Unknown PVR format.');
    }
  }

}

function _parseV3(pvrDatas) {
  const header = pvrDatas.header;
  let bpp, format;
  const metaLen = header[12],
        pixelFormat = header[2],
        height = header[6],
        width = header[7],
        // numSurfs = header[ 9 ],
  numFaces = header[10],
        numMipmaps = header[11];

  switch (pixelFormat) {
    case 0:
      // PVRTC 2bpp RGB
      bpp = 2;
      format = THREE.RGB_PVRTC_2BPPV1_Format;
      break;

    case 1:
      // PVRTC 2bpp RGBA
      bpp = 2;
      format = THREE.RGBA_PVRTC_2BPPV1_Format;
      break;

    case 2:
      // PVRTC 4bpp RGB
      bpp = 4;
      format = THREE.RGB_PVRTC_4BPPV1_Format;
      break;

    case 3:
      // PVRTC 4bpp RGBA
      bpp = 4;
      format = THREE.RGBA_PVRTC_4BPPV1_Format;
      break;

    default:
      console.error('THREE.PVRLoader: Unsupported PVR format:', pixelFormat);
  }

  pvrDatas.dataPtr = 52 + metaLen;
  pvrDatas.bpp = bpp;
  pvrDatas.format = format;
  pvrDatas.width = width;
  pvrDatas.height = height;
  pvrDatas.numSurfaces = numFaces;
  pvrDatas.numMipmaps = numMipmaps;
  pvrDatas.isCubemap = numFaces === 6;
  return _extract(pvrDatas);
}

function _parseV2(pvrDatas) {
  const header = pvrDatas.header;
  const headerLength = header[0],
        height = header[1],
        width = header[2],
        numMipmaps = header[3],
        flags = header[4],
        // dataLength = header[ 5 ],
  // bpp =  header[ 6 ],
  // bitmaskRed = header[ 7 ],
  // bitmaskGreen = header[ 8 ],
  // bitmaskBlue = header[ 9 ],
  bitmaskAlpha = header[10],
        // pvrTag = header[ 11 ],
  numSurfs = header[12];
  const TYPE_MASK = 0xff;
  const PVRTC_2 = 24,
        PVRTC_4 = 25;
  const formatFlags = flags & TYPE_MASK;
  let bpp, format;

  const _hasAlpha = bitmaskAlpha > 0;

  if (formatFlags === PVRTC_4) {
    format = _hasAlpha ? THREE.RGBA_PVRTC_4BPPV1_Format : THREE.RGB_PVRTC_4BPPV1_Format;
    bpp = 4;
  } else if (formatFlags === PVRTC_2) {
    format = _hasAlpha ? THREE.RGBA_PVRTC_2BPPV1_Format : THREE.RGB_PVRTC_2BPPV1_Format;
    bpp = 2;
  } else {
    console.error('THREE.PVRLoader: Unknown PVR format:', formatFlags);
  }

  pvrDatas.dataPtr = headerLength;
  pvrDatas.bpp = bpp;
  pvrDatas.format = format;
  pvrDatas.width = width;
  pvrDatas.height = height;
  pvrDatas.numSurfaces = numSurfs;
  pvrDatas.numMipmaps = numMipmaps + 1; // guess cubemap type seems tricky in v2
  // it juste a pvr containing 6 surface (no explicit cubemap type)

  pvrDatas.isCubemap = numSurfs === 6;
  return _extract(pvrDatas);
}

function _extract(pvrDatas) {
  const pvr = {
    mipmaps: [],
    width: pvrDatas.width,
    height: pvrDatas.height,
    format: pvrDatas.format,
    mipmapCount: pvrDatas.numMipmaps,
    isCubemap: pvrDatas.isCubemap
  };
  const buffer = pvrDatas.buffer;
  let dataOffset = pvrDatas.dataPtr,
      dataSize = 0,
      blockSize = 0,
      blockWidth = 0,
      blockHeight = 0,
      widthBlocks = 0,
      heightBlocks = 0;
  const bpp = pvrDatas.bpp,
        numSurfs = pvrDatas.numSurfaces;

  if (bpp === 2) {
    blockWidth = 8;
    blockHeight = 4;
  } else {
    blockWidth = 4;
    blockHeight = 4;
  }

  blockSize = blockWidth * blockHeight * bpp / 8;
  pvr.mipmaps.length = pvrDatas.numMipmaps * numSurfs;
  let mipLevel = 0;

  while (mipLevel < pvrDatas.numMipmaps) {
    const sWidth = pvrDatas.width >> mipLevel,
          sHeight = pvrDatas.height >> mipLevel;
    widthBlocks = sWidth / blockWidth;
    heightBlocks = sHeight / blockHeight; // Clamp to minimum number of blocks

    if (widthBlocks < 2) widthBlocks = 2;
    if (heightBlocks < 2) heightBlocks = 2;
    dataSize = widthBlocks * heightBlocks * blockSize;

    for (let surfIndex = 0; surfIndex < numSurfs; surfIndex++) {
      const byteArray = new Uint8Array(buffer, dataOffset, dataSize);
      const mipmap = {
        data: byteArray,
        width: sWidth,
        height: sHeight
      };
      pvr.mipmaps[surfIndex * pvrDatas.numMipmaps + mipLevel] = mipmap;
      dataOffset += dataSize;
    }

    mipLevel++;
  }

  return pvr;
}

THREE.PVRLoader = PVRLoader;
} )();
